package org.sepheriel.airy2d;

import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.EXTFramebufferObject;
import org.lwjgl.opengl.GL11;
import org.sepheriel.airy2d.event.EngineEvents;
import org.sepheriel.airy2d.event.Event;
import org.sepheriel.airy2d.event.EventManager;
import org.sepheriel.airy2d.event.EventReceiver;
import org.sepheriel.airy2d.game.EngineInterface;
import org.sepheriel.airy2d.math.Util;
import org.sepheriel.airy2d.math.Vec2;
import org.sepheriel.airy2d.utils.Utils;


public class Engine implements EventReceiver{
	
	
	/** Singleton instance of this Engine */
	public static Engine instance = null;
	/** connection to game */
	private EngineInterface child = null;
	/** title of game */
	private String title;
	/** screen resolution */
	private Vec2 resolution = new Vec2(800, 600);
	/** fullscreen flag */
	private boolean isFullscreen = false;
	/** vsync flag */
	private boolean vsync = true;
	/** post processing flag */
	private boolean PostProcessing = true;
	/** event manager **/
	private EventManager eventEngine = new EventManager();
	/** fps */
	private int fps = 0;
	/** last frame (for fps) */
	private long lastFrame;
	/** last FPS */
	private long lastFPS;
	/** tex id */
	private int colorTextureID;
	/** framebuffer (for post processing) */
	private int framebufferID;
	/** delta value (for fps, logic, usw.) */
	private int delta;
	
	
	public void connectInterface(EngineInterface _child)
	{
		child = _child;
	}
	
	public void init()
	{
		try {
			Display.setDisplayMode(new DisplayMode(getResolution().getX(),getResolution().getY()));
			Display.setVSyncEnabled(vsync);
			Display.setTitle(this.title);
			Display.create();
		} catch (LWJGLException e) {
			e.printStackTrace();
			System.exit(0);
		}
		Utils.dump("initialisation succeded!", 1);
		eventEngine.triggerEvent(EngineEvents.INIT_DONE);
	}
	
	public void start()
	{
		eventEngine.subscribe(this);
		eventEngine.triggerEvent(EngineEvents.START_ENGINE);
	}
	
	public void onEvent(Event _event)
	{
		if(_event == EngineEvents.START_ENGINE)
		{
			init();
		}
		if(_event == EngineEvents.INIT_DONE)
		{
			initGL();
		}
		if(_event == EngineEvents.GRAPHICS_READY)
		{
			exec();
		}
		if(_event == EngineEvents.CLOSE_REQUEST)
		{
			Display.destroy();
			Utils.dump("game sucessfully destroyed!", 1);
		}
	}
	
	public void initGL() {
		GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST);
		GL11.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_NICEST);
    	GL11.glEnable(GL11.GL_BLEND);
    	GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
    	GL11.glViewport(0,0,getResolution().getX(),getResolution().getY());
    	GL11.glMatrixMode(GL11.GL_MODELVIEW);
    	GL11.glMatrixMode(GL11.GL_PROJECTION);
		GL11.glLoadIdentity();
		GL11.glOrtho(0, getResolution().getX(), getResolution().getY(), 0, 1, -1);
		GL11.glMatrixMode(GL11.GL_MODELVIEW);
		
		if(PostProcessing)
		{
			setFramebufferID(EXTFramebufferObject.glGenFramebuffersEXT());
			colorTextureID = GL11.glGenTextures();
	
			EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, getFramebufferID());
			GL11.glBindTexture(GL11.GL_TEXTURE_2D, colorTextureID);
			GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
			GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, getResolution().getX(), getResolution().getY(), 0,GL11.GL_RGBA, GL11.GL_INT, (java.nio.ByteBuffer) null);	
			EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT,GL11.GL_TEXTURE_2D, colorTextureID, 0); 
	
			EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0);	
		}
		child.init();
		Utils.dump("graphics intialisation succeded!", 1);
		eventEngine.triggerEvent(EngineEvents.GRAPHICS_READY);
	}
	
	public void exec()
	{
		getDelta();
		lastFPS = getTime();
		Utils.dump("start rendering!", 1);
		while (!Display.isCloseRequested())
		{
			eventEngine.execute();
			delta = getDelta();
			child.update(delta);
			
				renderEngine();
			
			Display.update();
			Display.sync(60);
		}
		eventEngine.triggerEvent(EngineEvents.CLOSE_REQUEST);
	}
	
	public void renderEngine()
	{
		if(PostProcessing) {
			
			EXTFramebufferObject.glBindFramebufferEXT( EXTFramebufferObject.GL_FRAMEBUFFER_EXT, getFramebufferID() );
			
			GL11.glClearColor(0f, 0f, 0f, 0f);
			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
			GL11.glLoadIdentity();
			GL11.glViewport( 0, 0, getResolution().getX(), getResolution().getY());	
		
				child.render();

			EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0);
			
			GL11.glClearColor(0f, 0f, 0f, 0f);
			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
			GL11.glLoadIdentity();		
			GL11.glViewport( 0, 0, getResolution().getX(), getResolution().getY());	
		
				child.postFX();
		
			GL11.glFlush();
			
		} else {
			GL11.glViewport( 0, 0, getResolution().getX(), getResolution().getY());
			GL11.glClearColor(0f, 0f, 0f, 0f);
			GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
			GL11.glLoadIdentity();
		
				child.render();
				
				GL11.glFlush();
		}
		
	}
	
	public long getTime() {
	    return (Sys.getTime() * 1000) / Sys.getTimerResolution();
	}
	
	
	public static Engine getInstance()
	{
		if(instance == null)
		{
			instance = new Engine();
		}
		return instance;
	}
	
	public int getDelta() {
	    long time = getTime();
	    int delta = (int) (time - lastFrame);
	    lastFrame = time;
 
	    return delta;
	}
	
	public void updateFPS() {
		if (getTime() - lastFPS > 1000) {
			Display.setTitle("FPS: " + getFps());
			setFps(0);
			lastFPS += 1000;
		}
		setFps(getFps() + 1);
	}

	public int getFps() {
		return fps;
	}

	public void setFps(int _fps) {
		fps = _fps;
	}
	
	public void setTitle(String _title)
	{
		title = _title;
	}
	
	public String getTitle() {
		return this.title;
	}

	public Vec2 getResolution() {
		return resolution;
	}

	public void setResolution(Vec2 _resolution)
	{
		resolution = _resolution;
	}

	public int getFramebufferID() {
		return framebufferID;
	}

	public void setFramebufferID(int framebufferID) {
		this.framebufferID = framebufferID;
	}
	
	public EventManager getEventManager()
	{
		return eventEngine;
	}
}